package org.example.system.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.RequiredArgsConstructor;
import org.example.system.common.constant.SystemConstants;
import org.example.system.common.enums.MenuTypeEnum;
import org.example.system.common.enums.StatusEnum;
import org.example.system.common.exception.BusinessException;
import org.example.system.common.model.Option;
import org.example.system.converter.MenuConverter;
import org.example.system.mapper.SysMenuMapper;
import org.example.system.model.bo.RouteBO;
import org.example.system.model.entity.SysMenu;
import org.example.system.model.form.MenuForm;
import org.example.system.model.query.MenuQuery;
import org.example.system.model.vo.MenuVO;
import org.example.system.model.vo.RouteVO;
import org.example.system.service.SysMenuService;
import org.example.system.service.SysRoleMenuService;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;
import java.util.stream.Collectors;

/**
 * 菜单业务实现类
 *
 * @author LY
 */
@Service
@RequiredArgsConstructor
public class SysMenuServiceImpl extends ServiceImpl<SysMenuMapper, SysMenu> implements SysMenuService {

    private final MenuConverter menuConverter;

    private final SysRoleMenuService roleMenuService;

    private static final Integer ENABLED = 1;

    /**
     * 获取角色权限(Code)集合
     *
     * @param roles 角色Code集合
     * @return 权限集合
     */
    @Override
    public Set<String> listRolePerms(Set<String> roles) {
        return this.baseMapper.listRolePerms(roles);
    }

    /**
     * 获取菜单选项
     *
     * @return 菜单选项集合
     */
    @Override
    public List<Option<Long>> listMenuOptions() {
        // 构建查询条件
        LambdaQueryWrapper<SysMenu> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.orderByAsc(SysMenu::getSort);

        // 查询菜单列表并进行空值保护
        List<SysMenu> menuList = this.list(queryWrapper);
        if (menuList == null) {
            menuList = Collections.emptyList();
        }

        // 构建菜单选项树
        Long rootNodeId = SystemConstants.ROOT_NODE_ID;
        return buildMenuOptions(rootNodeId, menuList);
    }

    /**
     * 获取菜单表单数据
     *
     * @param id 菜单ID
     * @return 菜单表单数据
     */
    @Override
    public MenuForm getMenuForm(Long id) {
        if (id == null) {
            throw new IllegalArgumentException("菜单ID不能为空");
        }

        SysMenu entity = this.getById(id);
        if (entity == null) {
            return null;
        }

        return menuConverter.entity2Form(entity);
    }

    /**
     * 菜单列表
     *
     * @param queryParams {@link MenuQuery}
     */
    @Override
    public List<MenuVO> listMenus(MenuQuery queryParams) {
        List<SysMenu> menus = this.list(new LambdaQueryWrapper<SysMenu>()
                .like(StrUtil.isNotBlank(queryParams.getKeywords()), SysMenu::getName, queryParams.getKeywords())
                .orderByAsc(SysMenu::getSort)
        );

        if (menus.isEmpty()) {
            return Collections.emptyList();
        }

        Set<Long> menuIds = new HashSet<>();
        Set<Long> parentIds = new HashSet<>();

        // 合并遍历，减少流操作次数
        for (SysMenu menu : menus) {
            menuIds.add(menu.getId());
            parentIds.add(menu.getParentId());
        }

        // 获取根节点ID（递归的起点），即父节点ID中不包含在部门ID中的节点，注意这里不能拿顶级菜单 O 作为根节点，因为菜单筛选的时候 O 会被过滤掉
        List<Long> rootIds = parentIds.stream()
                .filter(id -> !menuIds.contains(id))
                .toList();

        // 使用递归函数来构建菜单树
        return rootIds.stream()
                .flatMap(rootId -> buildMenuTree(rootId, menus).stream())
                .collect(Collectors.toList());
    }

    /**
     * 新增/修改菜单
     */
    @CacheEvict(cacheNames = "menu", key = "'" + SystemConstants.CACHE_KEY_ROUTES + "'")
    public boolean saveMenu(MenuForm menuForm) {
        if (menuForm == null) {
            return false;
        }

        MenuTypeEnum menuType = menuForm.getType();
        if (menuType == null) {
            return false;
        }

        // 处理外链和目录路径
        String path = menuForm.getPath();
        Long parentId = menuForm.getParentId();

        if (menuType == MenuTypeEnum.CATALOG) {
            if (parentId != null && parentId == 0 && path != null && !path.startsWith("/")) {
                menuForm.setPath("/" + path);
            }
            menuForm.setComponent("Layout");
        } else if (menuType == MenuTypeEnum.EXTLINK) {
            menuForm.setComponent(null);
        }

        try {
            String treePath = generateMenuTreePath(parentId);
            if (treePath == null || treePath.isEmpty()) {
                // 可选：抛出异常或记录日志
                return false;
            }

            SysMenu entity = menuConverter.form2Entity(menuForm);
            entity.setTreePath(treePath);

            boolean result = this.saveOrUpdate(entity);
            if (result && menuForm.getId() != null) {
                roleMenuService.refreshRolePermsCache();
            }
            return result;

        } catch (Exception e) {
            throw new RuntimeException("保存或更新菜单失败", e);
        }
    }

    /**
     * 删除菜单
     *
     * @param id 菜单ID
     * @return 是否删除成功
     */
    @Override
    @CacheEvict(cacheNames = "menu", key = "'" + SystemConstants.CACHE_KEY_ROUTES + "'")
    public boolean deleteMenu(Long id) {
        LambdaQueryWrapper<SysMenu> wrapper = new LambdaQueryWrapper<>();
        wrapper.eq(SysMenu::getId, id)
                .or()
                .apply("CONCAT(',', tree_path, ',') LIKE CONCAT('%,', {0}, ',%')", id);

        boolean result = this.remove(wrapper);

        // 刷新角色权限缓存
        if (result) {
            roleMenuService.refreshRolePermsCache();
        }

        return result;
    }

    /**
     * 部门路径生成
     *
     * @param parentId 父ID
     * @return 父节点路径以英文逗号(, )分割，eg: 1,2,3
     */
    private String generateMenuTreePath(Long parentId) {
        if (parentId == null) {
            return "";
        }
        if (SystemConstants.ROOT_NODE_ID.equals(parentId)) {
            return String.valueOf(parentId);
        } else {
            SysMenu parent = this.getById(parentId);
            if (parent == null) {
                return "";
            }
            String treePath = parent.getTreePath();
            if (treePath == null || treePath.isEmpty()) {
                return String.valueOf(parent.getId());
            }
            return treePath + "," + parent.getId();
        }
    }

    /**
     * 递归生成菜单列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单列表
     */
    private List<MenuVO> buildMenuTree(Long parentId, List<SysMenu> menuList) {
        // 防止空指针
        List<SysMenu> safeMenuList = CollectionUtil.emptyIfNull(menuList);

        // 过滤当前层级的菜单并递归构建子树
        return safeMenuList.stream()
                .filter(menu -> Objects.equals(menu.getParentId(), parentId))
                .map(entity -> {
                    MenuVO menuVO = convertEntityToVo(entity);
                    List<MenuVO> children = buildMenuTree(entity.getId(), safeMenuList);
                    menuVO.setChildren(children);
                    return menuVO;
                })
                .toList();
    }

    /**
     * 将 SysMenu 实体转换为 MenuVO（可进一步提取为工具类方法）
     */
    private MenuVO convertEntityToVo(SysMenu entity) {
        return menuConverter.entity2Vo(entity);
    }

    /**
     * 递归生成菜单下拉层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 菜单下拉列表
     */
    private List<Option<Long>> buildMenuOptions(Long parentId, List<SysMenu> menuList) {
        // 空值保护
        if (menuList == null || menuList.isEmpty()) {
            return new ArrayList<>();
        }

        // 构建父ID到子菜单列表的映射，提升查找效率
        Map<Long, List<SysMenu>> parentToChildrenMap = new HashMap<>();
        for (SysMenu menu : menuList) {
            Long pid = menu.getParentId();
            parentToChildrenMap.computeIfAbsent(pid, k -> new ArrayList<>()).add(menu);
        }

        // 递归构建选项
        return buildOptions(parentId, parentToChildrenMap);
    }

    /**
     * 实际递归构建方法
     */
    private List<Option<Long>> buildOptions(Long parentId, Map<Long, List<SysMenu>> parentToChildrenMap) {
        List<Option<Long>> menuOptions = new ArrayList<>();

        List<SysMenu> children = parentToChildrenMap.getOrDefault(parentId, Collections.emptyList());
        for (SysMenu menu : children) {
            Option<Long> option = new Option<>(menu.getId(), menu.getName());

            List<Option<Long>> subMenuOptions = buildOptions(menu.getId(), parentToChildrenMap);
            if (!subMenuOptions.isEmpty()) {
                option.setChildren(subMenuOptions);
            }

            menuOptions.add(option);
        }

        return menuOptions;
    }

    /**
     * 获取路由列表
     */
    @Override
    @Cacheable(cacheNames = "menu", key = "'" + SystemConstants.CACHE_KEY_ROUTES + "'")
    public List<RouteVO> listRoutes() {
        try {
            // 获取根节点ID，用于构建路由树的起点
            Long rootNodeId = SystemConstants.ROOT_NODE_ID;
            List<RouteBO> menuList = this.baseMapper.listRoutes();

            // 构建路由树结构
            return buildRoutes(rootNodeId, menuList);
        } catch (Exception e) {
            throw new BusinessException("获取路由列表失败，请稍后重试");
        }
    }

    /**
     * 递归生成菜单路由层级列表
     *
     * @param parentId 父级ID
     * @param menuList 菜单列表
     * @return 路由层级列表
     */
    private List<RouteVO> buildRoutes(Long parentId, List<RouteBO> menuList) {
        // 参数校验
        if (menuList == null) {
            return new ArrayList<>();
        }

        // 构建父ID到子菜单的映射关系
        Map<Long, List<RouteBO>> parentMenuMap = new HashMap<>();
        for (RouteBO menu : menuList) {
            Long pid = menu.getParentId();
            parentMenuMap.computeIfAbsent(pid, k -> new ArrayList<>()).add(menu);
        }

        // 从根节点开始构建
        return buildSubRoutes(parentId, parentMenuMap);
    }

    /**
     * 根据父ID和菜单映射表递归构建路由
     */
    private List<RouteVO> buildSubRoutes(Long parentId, Map<Long, List<RouteBO>> parentMenuMap) {
        List<RouteVO> routeList = new ArrayList<>();

        List<RouteBO> children = parentMenuMap.getOrDefault(parentId, Collections.emptyList());

        for (RouteBO menu : children) {
            RouteVO routeVO = toRouteVo(menu);

            List<RouteVO> subRoutes = buildSubRoutes(menu.getId(), parentMenuMap);
            if (!subRoutes.isEmpty()) {
                routeVO.setChildren(subRoutes);
            }

            routeList.add(routeVO);
        }

        return routeList;
    }

    /**
     * 根据RouteBO创建RouteVO
     */
    private RouteVO toRouteVo(RouteBO routeBO) {
        if (routeBO == null) {
            return new RouteVO();
        }

        RouteVO routeVO = new RouteVO();

        // 路由 name 需要驼峰，首字母大写
        String routeName = StringUtils.capitalize(StrUtil.toCamelCase(routeBO.getPath(), '-'));
        routeVO.setName(routeName);
        routeVO.setPath(routeBO.getPath());
        routeVO.setComponent(routeBO.getComponent());

        RouteVO.Meta meta = buildMetaFromRouteBO(routeBO);
        routeVO.setMeta(meta);

        return routeVO;
    }

    /**
     * 构建路由 Meta 对象
     */
    private RouteVO.Meta buildMetaFromRouteBO(RouteBO routeBO) {
        RouteVO.Meta meta = new RouteVO.Meta();
        meta.setTitle(routeBO.getName());
        meta.setIcon(routeBO.getIcon());
        meta.setRoles(routeBO.getRoles());
        meta.setHidden(StatusEnum.DISABLE.getValue().equals(routeBO.getVisible()));

        // 【菜单】是否开启页面缓存
        if (MenuTypeEnum.MENU.equals(routeBO.getType())
                && ObjectUtil.equals(routeBO.getKeepAlive(), ENABLED)) {
            meta.setKeepAlive(true);
        }

        // 【目录】只有一个子路由是否始终显示
        if (MenuTypeEnum.CATALOG.equals(routeBO.getType())
                && ObjectUtil.equals(routeBO.getAlwaysShow(), ENABLED)) {
            meta.setAlwaysShow(true);
        }

        return meta;
    }
}
